#include "MmapSHMObject.h"
#include <cstddef>
#include "Util.h"

namespace xncc {
namespace shared {

    MmapSHMObject::MmapSHMObject(SHM_FILE_MODE fileMode,
                                 SHM_MODE      mode,
                                 const char*   name,
                                 long long     fileSize,
                                 unsigned int  perm)
        : shmHandle_(-1), base_(nullptr), size_(static_cast<std::size_t>(fileSize))
    {
        int oflag = 0;
        if (mode == SHM_MODE::READ_ONLY) {
            oflag |= O_RDONLY;
        }
        else if (mode == SHM_MODE::READ_WRITE) {
            oflag |= O_RDWR;
        }
        else {
            throw logic_exception("error mode");
        }
        switch (fileMode) {
            case SHM_FILE_MODE::OPEN: {
                shmHandle_ = shm_open(name, oflag, perm);
            } break;
            case SHM_FILE_MODE::CREATE: {
                oflag |= (O_CREAT | O_EXCL);
                shmHandle_ = shm_open(name, oflag, perm);
                if (shmHandle_ >= 0) {
                    ::fchmod(shmHandle_, perm);
                    while (true) {
                        if (0 != ftruncate(shmHandle_, fileSize)) {
                            if (errno == EINTR)
                                continue;
                            throw std::system_error(std::error_code(errno, std::system_category()), "ftruncate failed");
                        }
                        else {
                            break;
                        }
                    }
                }
            } break;
            case SHM_FILE_MODE::OPENORCREATE: {
                while (true) {
                    shmHandle_ = shm_open(name, oflag | (O_CREAT | O_EXCL), perm);
                    if (shmHandle_ >= 0) {
                        ::fchmod(shmHandle_, perm);
                        while (true) {
                            if (0 != ftruncate(shmHandle_, fileSize)) {
                                if (errno == EINTR)
                                    continue;
                                throw std::system_error(std::error_code(errno, std::system_category()),
                                                        "ftruncate failed");
                            }
                            else {
                                break;
                            }
                        }
                    }
                    // If already exists, try to open
                    else if (errno == EEXIST) {
                        shmHandle_ = shm_open(name, oflag, perm);
                        // If open fails and errno tells the file does not exist
                        //(shm was removed between creation and opening tries), just retry
                        if (shmHandle_ < 0 && errno == ENOENT) {
                            continue;
                        }
                    }
                    // Exit retries
                    break;
                }
            } break;
            default: {
                throw logic_exception("error shm file mode");
            }
        }
        if (shmHandle_ < 0) {
            throw std::system_error(std::error_code(errno, std::system_category()), "file failed");
        }

        int prot  = 0;
        int flags = 0;
        switch (mode) {
            case SHM_MODE::READ_ONLY:
                prot |= PROT_READ;
                flags |= MAP_SHARED;
                break;
            case SHM_MODE::READ_WRITE:
                prot |= (PROT_WRITE | PROT_READ);
                flags |= MAP_SHARED;
                break;
            default: {
            } break;
        }

        void* base = mmap(nullptr, static_cast<std::size_t>(fileSize), prot, flags, shmHandle_, 0);
        // Check if mapping was successful
        if (base == MAP_FAILED) {
            throw std::system_error(std::error_code(errno, std::system_category()), "mmap failed");
        }
        base_ = base;
    }

    bool MmapSHMObject::remove(const char* name) { return 0 == shm_unlink(name); }

    MmapSHMObject::~MmapSHMObject()
    {
        if (base_ != nullptr) {
            munmap(base_, (size_));
        }
        if (shmHandle_ != -1) {
            ::close(shmHandle_);
            shmHandle_ = -1;
        }
    }

}  // namespace shared
}  // namespace xncc
